import sys
import time
import os
import json
import csv
import datetime

import cv2
import numpy as np
import pyqtgraph as pg
from serial import Serial
from PyQt5.QtWidgets import QApplication, QListView, QMessageBox, QDesktopWidget, QMainWindow, QDialog, QComboBox, QVBoxLayout, QPushButton, QLabel, QFileDialog
from PyQt5.QtCore import QObject, QTimer
from PyQt5.QtGui import QIcon
from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo
from PyQt5.QtNetwork import QHostAddress


from DialogUI import *
from InterfaceUI import *
from TestWindow import *
from styles import *
from server import Server
# === 全局配置 ===
pg.setConfigOptions(leftButtonPan=False)
pg.setConfigOption('background', '#fffcdc')
pg.setConfigOption('foreground', '#391d00')
pen1=pg.mkPen(color='#391d00', width=2)
cmd_force = '01 03 00 50 00 02 C4 1A'
proj_num_path = ''
''' 总控制界面 '''
class InterfaceWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.ui = Ui_InterfaceWindow()
        self.ui.setupUi(self)
        self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
        
        # 串口对象
        self.Serial = QSerialPort()
        # =============== 创建各界面控制类 ==============
        self.MainWindow_Init() # 主界面初始化
        self.ProjWindow = ProjWindow(self)  # 项目界面
        self.TestWindow = TestWindow(self) # 测试界面
        self.ForceWindow = ForceWindow(self)  # 力学分析界面
        self.CameraWindow = CameraWindow(self)  # 三维重构界面
        self.DigitalTwinWindow = DigitalTwinWindow(self)  # 数字孪生界面
        self.centerUI()
        self.show()

    def setbutton(self,button):
        '''选中按钮后的操作'''
        self.ui.pushButton_project.setStyleSheet("background-color:transparent")
        self.ui.pushButton_test.setStyleSheet("background-color:transparent")
        self.ui.pushButton_control.setStyleSheet("background-color:transparent")
        self.ui.pushButton_force.setStyleSheet("background-color:transparent")
        self.ui.pushButton_img.setStyleSheet("background-color:transparent")
        self.ui.pushButton_help.setStyleSheet("background-color:transparent")
        button.setStyleSheet("background-color:rgb(48,40,91)")
        if button == self.ui.pushButton_project :
            self.ui.stackedWidget.setCurrentIndex(0)
        if button == self.ui.pushButton_test :
            self.ui.stackedWidget.setCurrentIndex(1)
        if button == self.ui.pushButton_control :
            self.ui.stackedWidget.setCurrentIndex(2)
        if button == self.ui.pushButton_force :
            self.ui.stackedWidget.setCurrentIndex(3)
        if button == self.ui.pushButton_img :
            self.ui.stackedWidget.setCurrentIndex(4)
        if button == self.ui.pushButton_help :
            self.ui.stackedWidget.setCurrentIndex(5)
        
    def MainWindow_Init(self):
        ''' 主界面配置 '''
        # 放大按键
        self.ui.pushButton_maximize.clicked.connect(self.restore_or_maximize_window)
        # 菜单栏按键
        self.ui.pushButton_project.clicked.connect(lambda: self.setbutton(self.ui.pushButton_project))
        self.ui.pushButton_test.clicked.connect(lambda: self.setbutton(self.ui.pushButton_test))
        self.ui.pushButton_control.clicked.connect(lambda: self.setbutton(self.ui.pushButton_control))
        self.ui.pushButton_force.clicked.connect(lambda: self.setbutton(self.ui.pushButton_force))
        self.ui.pushButton_img.clicked.connect(lambda: self.setbutton(self.ui.pushButton_img))
        self.ui.pushButton_help.clicked.connect(lambda: self.setbutton(self.ui.pushButton_help))
        # 选中项目界面
        self.ui.stackedWidget.setCurrentIndex(0)
        # 欢迎界面
        self.welcome_text = ""
        self.welcome_count = 0
        self.timer1 = QTimer()
        self.timer1.timeout.connect(self.update_text)
        self.type_text("欢迎使用摩擦初号机")
        # 项目数据路径
        self.proj_path = ''
        self.proj_name = ''
        self.proj_num = ''
        self.proj_user = ''
        self.proj_date = ''
    
    def restore_or_maximize_window(self):
        ''' 放大缩小 '''
        if self.isMaximized():
            self.showNormal()
            self.ui.pushButton_maximize.setIcon(QtGui.QIcon(u":/icons/icon/Resources/icons/maximize.png"))
        else:
            self.showMaximized()
            self.ui.pushButton_maximize.setIcon(QtGui.QIcon(u":/icons/icon/Resources/icons/minimize.png"))

    def mousePressEvent(self, event):
        ''' 拖动 '''
        if event.button() == QtCore.Qt.LeftButton and self.isMaximized() == False:
            self.m_flag = True
            self.m_Position = event.globalPos() - self.pos()  # 获取鼠标相对窗口的位置
            event.accept()
            self.setCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))  # 更改鼠标图标

    def mouseMoveEvent(self, mouse_event):
        if QtCore.Qt.LeftButton and self.m_flag:
            self.move(mouse_event.globalPos() - self.m_Position)  # 更改窗口位置
            mouse_event.accept()

    def mouseReleaseEvent(self, mouse_event):
        self.m_flag = False
        self.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))

    def closeEvent(self, e):
        self.DigitalTwinWindow.sendMessage("_to_unity_abort_");
        QMainWindow.closeEvent(e)

    def centerUI(self):
        ''' 让窗体位于屏幕正中间 '''
        # 设置窗体大小
        # self.resize(400, 300)
        # 获取屏幕分辨率
        screen = QDesktopWidget().screenGeometry()
        # 获取窗体大小
        size = self.geometry()
        # 计算窗体左上角坐标
        self.move(int((screen.width() - size.width()) / 2), int((screen.height() - size.height()) / 2))

    def type_text(self, txt):
        ''' 动态显示滚动欢迎信息 '''
        self.welcome_text = txt
        self.welcome_count = 0
        self.timer1.stop()
        self.timer1.start(500)

    def update_text(self):
        self.welcome_count += 1
        self.ui.label_welcome.setText(self.welcome_text[0:self.welcome_count])

        if self.welcome_count >= len(self.welcome_text):
            self.welcome_count = 0
            self.timer1.stop()

    def Warning_Set(self, text):
        ''' 警告消息框设置 '''
        msg_box = QMessageBox()
        msg_box.setStyleSheet(msg_box_style)
        msg_box.setWindowTitle("警告")
        if text == "串口打开失败":
            msg_box.setText("串口打开失败") 
        elif text == "HEX格式错误":
            msg_box.setText("HEX格式错误")
        elif text == "串口未打开":
            msg_box.setText("串口未打开")
        else:
            msg_box.setText(text)
        msg_box.setIcon(QMessageBox.Warning)
        msg_box.setWindowFlag(QtCore.Qt.FramelessWindowHint)  # 隐藏边框
        msg_box.exec_()

''' 实验项目创建界面 '''
class ProjWindow(QMainWindow):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.ui = Ui_DialogWindow()
        self.ui.setupUi(self)
        self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
        # 重要变量
        self.proj_path = os.path.abspath('proj')  # 项目路径
        self.proj_num_path = ''
        # 信号与槽连接
        self.parent.ui.pushButton_new.clicked.connect(self.show_proj_dialog)
        self.parent.ui.pushButton_open.clicked.connect(self.open_proj)
        self.ui.pushButton_proj_confirm.clicked.connect(self.record_confirm)
        # 预设定
        self.ui.lineEdit_proj_time.setText(time.strftime('%Y-%m-%d %H:%M', time.localtime(time.time())))  # 预先设置实验日期

    # 新建实验项目
    def show_proj_dialog(self):
        self.show()

    # 打开和查看已有实验项目
    def open_proj(self):
        self.proj_num_path = QFileDialog.getExistingDirectory(self, "选取文件夹", './proj')
        # 若取消文件打开，直接结束
        if not self.proj_num_path:
            return
        else:
            print('【项目】打开项目 路径', self.proj_num_path)
            proj_num_path = self.proj_num_path
            print(proj_num_path)
            with open(os.path.join(self.proj_num_path, 'basic_info.json'), 'r') as file:
                data = json.load(file)
                self.parent.proj_path = self.proj_num_path
                self.parent.proj_name = data['name']
                self.parent.proj_num = data['num']
                self.parent.proj_user = data['user']
                self.parent.proj_date = data['date']
                file.close()
            print('【项目】打开项目 信息', '分析对象:', self.parent.proj_name, '对象编号:', self.parent.proj_num, '分析人员:', self.parent.proj_user, '分析时间:', self.parent.proj_date)

    
    # 新建项目
    def record_confirm(self):
        # 获取信息
        self.parent.name = self.ui.lineEdit_proj_name.text()  # 分析对象
        self.parent.num = self.ui.lineEdit_proj_num.text()  # 对象编号
        self.parent.user = self.ui.lineEdit_proj_user.text()  # 分析人员
        self.parent.date = self.ui.lineEdit_proj_time.text()  # 分析时间
        if not (self.parent.name and self.parent.num and self.parent.user):
            QMessageBox.warning(self, "警告", "请重新输入完整的实验信息",
                                      QMessageBox.Ok, QMessageBox.Ok)
        else:
            # 创建文件夹及保存实验信息
            self.proj_num_path = os.path.join(self.proj_path, self.parent.num)
            self.parent.proj_path = self.proj_num_path
            proj_num_path = self.proj_num_path
            print(proj_num_path)
            print('【项目】新建项目 路径', self.parent.proj_path)
            if os.path.exists(self.proj_num_path):
                QMessageBox.warning(self, "警告", "实验编号已存在，请重新输入",
                                          QMessageBox.Ok, QMessageBox.Ok)
            else:
                print('【项目】新建项目 路径', self.proj_num_path)
                os.makedirs(os.path.join(self.proj_num_path, 'CapImages_SuperEyes'))  # 存放图像数据
                os.makedirs(os.path.join(self.proj_num_path, 'CapImages_Hayear'))     # 存放图像数据
                os.makedirs(os.path.join(self.proj_num_path, 'force'))  # 存放力学数据
                basic_info_path = os.path.join(self.proj_num_path, 'basic_info.json')
                with open(basic_info_path, 'w') as file:
                    data = {
                        "name": self.parent.name,
                        "num": self.parent.num,
                        "user": self.parent.user,
                        "date": self.parent.date
                    }
                    json.dump(data, file)
                    file.close()
                self.close()

''' 力学分析界面 '''
class ForceWindow(QObject):
    def __init__(self, parent):
        super(ForceWindow, self).__init__()
        # 重要变量
        self.ui = parent.ui
        self.parent = parent
        self.Serial = parent.Serial  # 串口对象
        self.count = 0  # 计数器
        self.interval = 200  # 采样间隔(ms)
        self.interval_num = 100  # 一个窗口里的数据点总数
        self.data_force = np.zeros(self.interval_num)  # 力传感器数据
        self.file_name = ''
        self.file_path = ''
        # 信号与槽函数的连接
        self.ui.pushButton_force_start.clicked.connect(self.on_pushButton_force_startClicked)
        self.ui.pushButton_force_openFile.clicked.connect(self.on_pushButton_force_openFileClicked)

        self.ui.lineEdit_1_1.returnPressed.connect(self.update_title)
        self.ui.lineEdit_1_3.returnPressed.connect(self.update_xlabel)
        self.ui.lineEdit_1_4.returnPressed.connect(self.update_ylabel)
        # 定时器
        self.timer = QtCore.QTimer() # 实例化一个计时器
        self.timer.timeout.connect(self.update) # 计时器信号连接到update()函数
        # 画布设置
        self.plot_data_1 = self.ui.PlotWidget_1.plot(pen=pen1)
        self.plot_data_2 = self.ui.PlotWidget_2.plot(pen=pen1)
        self.lr = pg.LinearRegionItem([30, 50])  # 初始选框, 起始区间
        self.ui.PlotWidget_1.addItem(self.lr)

        self.ui.PlotWidget_1.setTitle("区域选择")
        self.ui.PlotWidget_2.setTitle("放大区域选择")

    def update_title(self):
        ''' 更改标题 '''
        title = self.ui.lineEdit_1_1.text()
        self.ui.PlotWidget_1.setTitle(title)
    def update_xlabel(self):
        ''' 更改x轴标签 '''
        xlabel = self.ui.lineEdit_1_3.text()
        self.ui.PlotWidget_1.setLabel(axis='bottom', text=xlabel)
    def update_ylabel(self):
        ''' 更改y轴标签 '''
        ylabel = self.ui.lineEdit_1_4.text()
        self.ui.PlotWidget_1.setLabel(axis='left', text=ylabel)
    
    def read_force(self):
        ''' 读取力传感器数据+保存数据到文件中 '''
        if self.Serial.isOpen() == True:
            # 串口助手界面HEX接收
            self.parent.SerialWindow.receive_flag = 1
            self.ui.pushButton_receive_form.setText("接收: HEX")
            self.ui.pushButton_receive_form.setStyleSheet("border-color: #2d94cd;\n color: #2d94cd;")
            # 发送命令
            cmd = cmd_force.replace(' ', '')  # 去掉空格
            self.Serial.write(bytes.fromhex(cmd))
            # 读取数据
            text = self.ui.textEdit_receive.toPlainText()
            text = text.replace(' ', '')
            text = text.replace('\n', '')
            return_data_hex = text[-18:]
            print("[力学界面]接收到的数据: ", return_data_hex)
            if return_data_hex[6:10] == 'FFFF' or return_data_hex[6:10] == 'ffff':
                force = int(return_data_hex[10:14], 16)
                force = force-65535
                print("[力学界面]拉力："+str(force)+'g')
            elif return_data_hex[6:10] == '0000':
                force = int(return_data_hex[10:14], 16)
                print("[力学界面]压力："+str(force)+'g')
            return force
        else:
            self.parent.Warning_Set("串口未打开")
            self.timer.stop()

    def update(self):
        force = self.read_force()  # 读取力传感器数据
        with open(self.file_path, 'a', newline='') as file:
            writer = csv.writer(file)
            timestamp = datetime.datetime.now()
            time = timestamp.strftime("%Y-%m-%d-%H-%M-%S")
            writer.writerow([time, str(force)])
        self.data_force = np.insert(self.data_force, 0, force)  # 将数据放入数组
        self.data_force = self.data_force[:-1]  # 删除最后一个数据
        # print(self.data_force)
        # 绘图
        self.plot_data_1.setData(self.data_force, pen=pen1)
        self.plot_data_2.setData(self.data_force, pen=pen1)
        self.ui.PlotWidget_2.setXRange(*self.lr.getRegion(), padding=0)

    def on_pushButton_force_startClicked(self):
        ''' 开始绘图 '''
        proj_path = os.path.join(self.parent.proj_path, 'force')
        # print(proj_path)
        if not os.path.exists(proj_path):
            self.parent.Warning_Set("请先新建实验项目")
            self.timer.stop()
        else:
            timestamp = datetime.datetime.now()
            self.file_name = timestamp.strftime("%Y-%m-%d-%H-%M-%S") + ".csv"
            self.file_path = os.path.join(proj_path, self.file_name)

        self.timer.start(self.interval) # 计时器间隔200毫秒(采样间隔)

        self.ui.pushButton_force_start.setIcon(QtGui.QIcon(u":/icons/icon/Resources/icons/pause.png"))
        self.ui.pushButton_force_start.clicked.disconnect(self.on_pushButton_force_startClicked)
        self.ui.pushButton_force_start.clicked.connect(self.draw_pause)
        self.lr.sigRegionChanged.connect(self.updatePlot)

    def draw_pause(self):
        self.timer.stop()

        self.ui.pushButton_force_start.setIcon(QtGui.QIcon(u":/icons/icon/Resources/icons/start.png"))
        self.ui.pushButton_force_start.clicked.disconnect(self.draw_pause)
        self.ui.pushButton_force_start.clicked.connect(self.on_pushButton_force_startClicked)

    def on_pushButton_force_openFileClicked(self):
        ''' 打开力学数据文件 '''
        proj_path = os.path.join(self.parent.proj_path, 'force')
        if not os.path.exists(proj_path):
            self.parent.Warning_Set("项目中没有力学数据文件")
        else:
            file_path = QFileDialog.getOpenFileName(self.parent, "选取文件", proj_path)

            # file_path = QFileDialog.getOpenFileName(self, "选取文件", proj_path, "CSV Files (*.csv)")
            if file_path[0]:
                self.file_path = file_path[0]
                self.data_force = np.loadtxt(self.file_path, delimiter=',', usecols=(1), skiprows=1, dtype=int)
                self.plot_data_1.setData(self.data_force, pen=pen1)
                self.plot_data_2.setData(self.data_force, pen=pen1)
                self.ui.PlotWidget_2.setXRange(*self.lr.getRegion(), padding=0)
                self.ui.lineEdit_1_1.setText(file_path[0])
                self.ui.lineEdit_1_3.setText('时间')
                self.ui.lineEdit_1_4.setText('力传感器数据')
                self.update_title()
                self.update_xlabel()
                self.update_ylabel()
                self.lr.sigRegionChanged.connect(self.updatePlot)
            
    # 更新绘图
    def updatePlot(self):
        self.ui.PlotWidget_2.setXRange(*self.lr.getRegion(), padding=0)

    # 更新区域选择
    def updateRegion(self):
        self.lr.setRegion(self.ui.PlotWidget_2.getViewBox().viewRange()[0])

''' 三维重构界面 '''
class CameraWindow(QObject):
    def __init__(self, parent):
        super(CameraWindow, self).__init__()
        # 重要变量        
        self.ui = parent.ui
        self.parent = parent
        self.save_image_bool = False  # 是否保存图片
        self.save_image_bool_2 = False
        self.CapNum = 1
        self.imgNum = 1
        self.light_ctrl = Serial("COM7", 9600)  # 若想直接运行UI可以先注释掉这两行
        self.light_ctrl.write("L0".encode())  # 关掉所有灯
        self.CAM_NUM_1 = 2  # 相机编号
        self.CAM_NUM_2 = 3
        self.LED_SEQ = ["L0", "L1", "L8", "L7", "L6", "L5", "L4", "L3", "L2"]  # LED序列
        self.COLOR_FACTOR = np.load("./Resources/factor.npy")  #相机颜色校正因子
        #计时器对象
        self.flash_timer = QTimer(self)
        self.flash_timer.start(1000)
        self.flash_timer.stop()
        # 相机对象
        self.cap = cv2.VideoCapture()
        self.cap2 = cv2.VideoCapture()
        # 定时获取相机图像
        self.timer_camera = QTimer(self)
        self.timer_camera.timeout.connect(self.show_camera)

        self.timer_camera2 = QTimer(self)
        self.timer_camera2.timeout.connect(self.show_camera2)
        # 连接按键和槽函数
        self.ui.pushButton_open_camera.clicked.connect(self.on_pushButton_open_camera_clicked)
        self.ui.pushButton_close_camera.clicked.connect(self.on_pushButton_close_camera_clicked)
        self.ui.pushButton_save_image.clicked.connect(self.on_pushButton_save_image_clicked)

        
        self.ui.pushButton_choose_imgs_supereyes.clicked.connect(self.on_pushButton_choose_imgs_supereyes_clicked)

        self.ui.pushButton_open_camera_2.clicked.connect(self.on_pushButton_open_camera_2_clicked)
        self.ui.pushButton_close_camera_2.clicked.connect(self.on_pushButton_close_camera_2_clicked)
        self.ui.pushButton_save_image_2.clicked.connect(self.on_pushButton_save_image_2_clicked)

        self.ui.pushButton_choose_imgs_auto.clicked.connect(self.on_pushButton_choose_imgs_auto_clicked)

    def show_camera(self):
        # print("show camera")
        ''' 显示相机图像 '''
        flag, self.image = self.cap.read()  # 读取相机图像

        self.image = (self.image * self.COLOR_FACTOR).astype(np.float32)     #色彩校正
        self.image = np.clip(self.image, 0, 255).astype(np.uint8)

        show = cv2.resize(self.image, (640, 480))  # 将调整图像大小
        show = cv2.cvtColor(show, cv2.COLOR_BGR2RGB)
        showImage = QtGui.QImage(show.data, show.shape[1], show.shape[0], QtGui.QImage.Format_RGB888)
        self.ui.label_camera_s.setPixmap(QtGui.QPixmap.fromImage(showImage))        

        #保存图片
        if self.save_image_bool == True:

            if not os.path.exists(os.path.join(self.parent.proj_path,'CapImages_SuperEyes','1')):
                self.CapNum = 1
                os.makedirs(os.path.join(self.parent.proj_path,'CapImages_SuperEyes',str(self.CapNum)))  # 存放图像数据
            else:

                
                self.CapNum = int(max(os.listdir(os.path.join(self.parent.proj_path,'CapImages_SuperEyes')))) + 1 #读取上一次拍摄的序号
                os.makedirs(os.path.join(self.parent.proj_path,'CapImages_SuperEyes',str(self.CapNum)))
               

            for light_num in range(0, 9):
                self.light_ctrl.write(self.LED_SEQ[light_num].encode())
                time.sleep(0.5)

                #显示图片设置
                flag, self.image = self.cap.read()
                ##
                self.image = self.image * self.COLOR_FACTOR
                self.image = np.clip(self.image, 0, 255).astype(np.uint8)

                show = cv2.resize(self.image, (640, 480))
                show = cv2.cvtColor(show, cv2.COLOR_BGR2RGB)
                
                showImage = QtGui.QImage(show.data, show.shape[1], show.shape[0], QtGui.QImage.Format_RGB888)
                self.ui.label_camera_s.setPixmap(QtGui.QPixmap.fromImage(showImage))

                time.sleep(0.5)
                
                #保存图片设置
                save_image = cv2.resize(self.image, (2048, 1536))
                #save_image = cv2.resize(self.image, (640, 480))
                save_image = cv2.cvtColor(save_image, cv2.COLOR_BGR2RGB)
                saveImage = QtGui.QImage(save_image.data, save_image.shape[1], save_image.shape[0], QtGui.QImage.Format_RGB888)
                
                # image_name = datetime.strftime('%Y%m%d%H%M%S', datetime.localtime(time())) + '.jpg'
                image_name = str(light_num) + '.jpg'
                image_path = os.path.join(self.parent.proj_path, 'CapImages_SuperEyes',str(self.CapNum), image_name)
                
                # saveImage.save(self.parent.proj_path + 'images' + os.sep + image_name)
                saveImage.save(image_path)
            
            self.light_ctrl.write("L0".encode())
            self.save_image_bool = False
            
    
    def on_pushButton_open_camera_clicked(self):
        # print("open camera")
        ''' 打开相机 '''
        self.cap.open(self.CAM_NUM_1)
        self.timer_camera.start(30)  # 30ms显示一次图像
        print(proj_num_path)
    
    def on_pushButton_close_camera_clicked(self):
        ''' 关闭相机 '''
        self.timer_camera.stop()
        self.ui.label_camera_s.clear()
    
    def on_pushButton_save_image_clicked(self):
        ''' 拍照 '''
        self.save_image_bool = True
        
    #以下为Hayear相机函数
    
    def on_pushButton_open_camera_2_clicked(self):
        # print("open camera")
        ''' 打开相机 '''
        self.cap2.open(self.CAM_NUM_2)
        self.timer_camera2.start(30)  # 30ms显示一次图像
        print(proj_num_path)
    
    def on_pushButton_close_camera_2_clicked(self):
        ''' 关闭相机 '''
        self.timer_camera2.stop()
        self.ui.label_camera_a.clear()
    
    def on_pushButton_save_image_2_clicked(self):
        ''' 拍照 '''
        self.save_image_bool_2 = True

    def show_camera2(self):
        # print("show camera")
        ''' 显示相机图像 '''
        flag, self.image = self.cap2.read()  # 读取相机图像

        self.image = (self.image * self.COLOR_FACTOR).astype(np.float32)     #色彩校正
        self.image = np.clip(self.image, 0, 255).astype(np.uint8)

        show = cv2.resize(self.image, (512, 320))  # 将调整图像大小
        show = cv2.cvtColor(show, cv2.COLOR_BGR2RGB)
        showImage = QtGui.QImage(show.data, show.shape[1], show.shape[0], QtGui.QImage.Format_RGB888)
        self.ui.label_camera_a.setPixmap(QtGui.QPixmap.fromImage(showImage))        

        #保存图片
        if self.save_image_bool_2 == True:

            #显示图片设置
            flag, self.image = self.cap2.read()
            ##
            
            show = cv2.resize(self.image, (512, 320))
            show = cv2.cvtColor(show, cv2.COLOR_BGR2RGB)
            
            showImage = QtGui.QImage(show.data, show.shape[1], show.shape[0], QtGui.QImage.Format_RGB888)
            self.ui.label_camera_a.setPixmap(QtGui.QPixmap.fromImage(showImage))

            time.sleep(0.5)
            
            #保存图片设置
            save_image = cv2.resize(self.image, (512, 320))
            save_image = cv2.cvtColor(save_image, cv2.COLOR_BGR2RGB)
            saveImage = QtGui.QImage(save_image.data, save_image.shape[1], save_image.shape[0], QtGui.QImage.Format_RGB888)
            
            if not os.path.exists(os.path.join(self.parent.proj_path, 'CapImages_Hayear','1.jpg')):
                image_name = '1.jpg'
                image_path = os.path.join(self.parent.proj_path, 'CapImages_Hayear',image_name)
                saveImage.save(image_path)
            else:
                img_name_list = []
                inputfile_dir = os.path.join(self.parent.proj_path, 'CapImages_Hayear') #文件路径
                
                
                for inputfile in os.listdir(inputfile_dir):
                    if os.path.splitext(inputfile)[1] == '.jpg':#判断是否为jpg文件
                            fileName = os.path.splitext(inputfile)[0]
                            img_name_list.append(fileName)           #将“文件名”装入列表
                            
                image_name_max = int(max(img_name_list)) + 1
                image_name = str(image_name_max) + '.jpg'
                image_path = os.path.join(self.parent.proj_path, 'CapImages_Hayear',image_name)
                saveImage.save(image_path)
            
            
            self.save_image_bool_2 = False


    def on_pushButton_choose_imgs_auto_clicked(self):
        filename, _ = QFileDialog.getOpenFileName(None,'select', os.path.join(self.parent.proj_path,'CapImages_Hayear'), 'Image files (*.tiff *.jpg *.png *.gif)')
        print(filename)
        if filename:

            filename_1 = os.path.basename(filename)
            self.imgNum = int(float(os.path.splitext(filename_1)[0]))
            
            frame = QtGui.QImage(filename)
            pix = QtGui.QPixmap.fromImage(frame)
            self.ui.label_camera_show_10.setPixmap(pix)
            self.ui.label_camera_show_10.setScaledContents (True)
            print(self.imgNum)

    #以上为Hayear相机函数
    
    def on_pushButton_choose_imgs_supereyes_clicked(self):
        filename0, _ = QFileDialog.getOpenFileName(None,'select', os.path.join(self.parent.proj_path,'CapImages_SuperEyes'), 'Image files (*.tiff *.jpg *.png *.gif)')

        if filename0:
            
            label_camera_show_list = [self.ui.label_camera_show_1,self.ui.label_camera_show_2,self.ui.label_camera_show_3,self.ui.label_camera_show_4,self.ui.label_camera_show_5,self.ui.label_camera_show_6,self.ui.label_camera_show_7,self.ui.label_camera_show_8,self.ui.label_camera_show_9]
           
            
            for i in range(0,9) :
                
                filename_current = filename0.replace('0',str(i))
                frame = QtGui.QImage(filename_current)
                pix = QtGui.QPixmap.fromImage(frame)
                label_camera_show_list[i].setPixmap(pix)
                label_camera_show_list[i].setScaledContents (True)

                



''' 数字孪生控制台 '''
# === 滑条范围 ===
# specimen_x -37 - 23
# machine_x 6 - 100
# machine_y 
# machine_z 0 - 300
# machine_rot 0 - 180
# eye_x -300 - 400
# eye_y 30 - 40
# eye_z -37 - 23
# === 实际范围 ===
# specimen_x 2 - 10 cm
class DigitalTwinWindow(QObject):
    def __init__(self, parent):
        super(DigitalTwinWindow, self).__init__()
        # 重要变量
        self.ui = parent.ui
        self.parent = parent
        self.twin_state = 1  # 数字孪生状态 0-观察模式 1-控制模式
        self.serial_flag = 0  # 0 未连接 1 已连接
        self.Serial = QSerialPort()  # 数字孪生连接stm32串口
        self.plainTextEdit = self.ui.plainTextEdit_twin_message
        self.serverRunning = False
        self.specimen_x = 0.156  # 下试样
        self.machine_x = 0.49  # 摩擦机器
        self.machine_y = -0.057
        self.machine_z = 0.127
        self.machine_rot = 90
        self.eye_x = -0.218  # 显微镜
        self.eye_y = 0.299
        self.eye_z = -0.335
        # 初始化
        self.server = self.makeServer()
        # 信号与槽函数的连接
        self.ui.pushButton_twin_connect.clicked.connect(self.onConnectClicked)  # 连接Unity
        self.ui.pushButton_twin_mode.clicked.connect(self.onModeClicked)  # 模式切换
        self.ui.horizontalSlider_twin_specimen_x.valueChanged.connect(self.onSpecimenXChanged)
        self.ui.horizontalSlider_twin_machine_x.valueChanged.connect(self.onMachineXChanged)
        self.ui.horizontalSlider_twin_machine_y.valueChanged.connect(self.onMachineYChanged)
        self.ui.horizontalSlider_twin_machine_z.valueChanged.connect(self.onMachineZChanged)
        self.ui.horizontalSlider_twin_machine_rot.valueChanged.connect(self.onMachineRotChanged)
        self.ui.horizontalSlider_twin_eye_x.valueChanged.connect(self.onEyeXChanged)
        self.ui.horizontalSlider_twin_eye_y.valueChanged.connect(self.onEyeYChanged)
        self.ui.horizontalSlider_twin_eye_z.valueChanged.connect(self.onEyeZChanged)
    
    def onConnectClicked(self):
        if self.serverRunning:
            return
        host = self.ui.lineEdit_twin_host.text()
        port = self.ui.lineEdit_twin_port.text()
        port = int(port)
        # ! host还没加
        if self.server.listen(QHostAddress.Any, port):
            self.serverRunning = True
            self.plainTextEdit.appendPlainText("Listening...")
        else:
            self.plainTextEdit.appendPlainText("Not listening")

    def onModeClicked(self):
        if self.twin_state == 0:
            self.twin_state = 1
            self.ui.label_twin_mode.setText("控制模式")
            self.ui.pushButton_twin_mode.setIcon(QtGui.QIcon(u":/icons/icon/Resources/icons/开关-off.png"))
            if self.serial_flag == 1:
                self.Serial.readyRead.disconnect(self.onDataReceived)
                self.Serial.close()
                self.serial_flag = 0
        else:
            self.twin_state = 0
            self.ui.label_twin_mode.setText("观察模式")
            self.ui.pushButton_twin_mode.setIcon(QtGui.QIcon(u":/icons/icon/Resources/icons/开关-on.png"))
            
            dialog = TwinSerialConnectionDialog(self.Serial)
            if dialog.exec_() == QDialog.Accepted:
                self.serial_flag = 1
                # 滑条都无法移动
                self.ui.horizontalSlider_twin_specimen_x.setEnabled(False)
                self.ui.horizontalSlider_twin_machine_x.setEnabled(False)
                self.ui.horizontalSlider_twin_machine_y.setEnabled(False)
                self.ui.horizontalSlider_twin_machine_z.setEnabled(False)
                self.ui.horizontalSlider_twin_machine_rot.setEnabled(False)
                self.ui.horizontalSlider_twin_eye_x.setEnabled(False)
                self.ui.horizontalSlider_twin_eye_y.setEnabled(False)
                self.ui.horizontalSlider_twin_eye_z.setEnabled(False)
                # 串口接收数据
                self.Serial.readyRead.connect(self.onSerialReceived)
            else:
                self.parent.Warning_Set("串口未打开")
    
    def onSerialReceived(self):
        # print("收到信号了")
        bytes_data = bytes(self.Serial.readAll())  # 二进制数据
        ascii_data = bytes_data.decode('ascii')
        # print(ascii_data)
        # 信号格式 distance 59 cm; speed 64 mm/s
        # 解析信号
        distance = ascii_data[ascii_data.find("distance")+9:ascii_data.find("cm;")]
        distance = distance.replace(" ", "")
        speed = ascii_data[ascii_data.find("speed")+6:ascii_data.find("mm/s")]
        speed = speed.replace(" ", "")
        # print('distance', distance, 'speed', speed)
        # 更新滑条
        distance = map_range(float(distance), 2, 10, -0.37, 0.23)
        self.ui.horizontalSlider_twin_specimen_x.setValue(int(float(distance) * 100))
        # self.ui.horizontalSlider_twin_machine_x.setValue(int(float(speed) * 100))

    def onNewClientConnected(self):
        self.plainTextEdit.appendPlainText("New client connected...")

    def onClientDisconnected(self):
        self.plainTextEdit.appendPlainText("Client disconnected")

    def onDataReceived(self, data):
        self.plainTextEdit.appendPlainText("From client: " + data)

        if "_to_pyqt_parameters_(" in data:
            txt = data.replace("_to_pyqt_parameters_(", "")
            txt = txt.replace(")", "")
            params = txt.split(";")

            specimen_x = params[0]
            machine_x = params[1]
            machine_y = params[2]
            machine_z = params[3]
            machine_rot = params[4]
            eye_x = params[5]
            eye_y = params[6]
            eye_z = params[7]

            specimen_x = specimen_x[specimen_x.find("=")+1:]
            machine_x = machine_x[machine_x.find("=")+1:]
            machine_y = machine_y[machine_y.find("=")+1:]
            machine_z = machine_z[machine_z.find("=")+1:]
            machine_rot = machine_rot[machine_rot.find("=")+1:]
            eye_x = eye_x[eye_x.find("=")+1:]
            eye_y = eye_y[eye_y.find("=")+1:]
            eye_z = eye_z[eye_z.find("=")+1:]

            specimen_x = specimen_x.replace(",", ".")
            machine_x = machine_x.replace(",", ".")
            machine_y = machine_y.replace(",", ".")
            machine_z = machine_z.replace(",", ".")
            machine_rot = machine_rot.replace(",", ".")
            eye_x = eye_x.replace(",", ".")
            eye_y = eye_y.replace(",", ".")
            eye_z = eye_z.replace(",", ".")

            self.specimen_x = float(specimen_x)
            self.machine_x = float(machine_x)
            self.machine_y = float(machine_y)
            self.machine_z = float(machine_z)
            self.machine_rot = float(machine_rot)
            self.eye_x = float(eye_x)
            self.eye_y = float(eye_y)
            self.eye_z = float(eye_z)

            self.ui.horizontalSlider_twin_specimen_x.setValue(int(self.specimen_x * 100))
            self.ui.horizontalSlider_twin_machine_x.setValue(int(self.machine_x * 100))
            self.ui.horizontalSlider_twin_machine_y.setValue(int(self.machine_y * 100))
            self.ui.horizontalSlider_twin_machine_z.setValue(int(self.machine_z * 1000))
            self.ui.horizontalSlider_twin_machine_rot.setValue(int(self.machine_rot))
            self.ui.horizontalSlider_twin_eye_x.setValue(int(self.eye_x * 100))
            self.ui.horizontalSlider_twin_eye_y.setValue(int(self.eye_y * 100))
            self.ui.horizontalSlider_twin_eye_z.setValue(int(self.eye_z * 100))

            self.ui.lineEdit_twin_specimen_x.setText(str(self.specimen_x))
            self.ui.lineEdit_twin_machine_x.setText(str(self.machine_x))
            self.ui.lineEdit_twin_machine_y.setText(str(self.machine_y))
            self.ui.lineEdit_twin_machine_z.setText(str(self.machine_z))
            self.ui.lineEdit_twin_machine_rot.setText(str(self.machine_rot))
            self.ui.lineEdit_twin_eye_x.setText(str(self.eye_x))
            self.ui.lineEdit_twin_eye_y.setText(str(self.eye_y))
            self.ui.lineEdit_twin_eye_z.setText(str(self.eye_z))     

    def onMessageSent(self, data):
        self.plainTextEdit.appendPlainText("Message sent: " + data)

    def makeServer(self):
        server = Server(self)
        server.newClientConnected.connect(self.onNewClientConnected)
        server.clientDisconnected.connect(self.onClientDisconnected)
        server.dataReceived.connect(self.onDataReceived)
        server.messageSent.connect(self.onMessageSent)
        return server
    
    # === 各种滑条控制代码 ===
    def onSpecimenXChanged(self):
        self.specimen_x = self.ui.horizontalSlider_twin_specimen_x.value() * 0.01
        self.ui.lineEdit_twin_specimen_x.setText(str(self.specimen_x))

        data = "_to_unity_parameters_(specimen_xpos={0};machine_xpos={1};machine_ypos={2};machine_zpos={3};machine_rot={4};eye_xpos={5};eye_ypos={6};eye_zpos={7};)"
        data = data.format(self.specimen_x, self.machine_x, self.machine_y, self.machine_z, self.machine_rot, self.eye_x, self.eye_y, self.eye_z)
        self.server.sendMessage(data)

    def onMachineXChanged(self):
        self.machine_x = self.ui.horizontalSlider_twin_machine_x.value() * 0.01
        self.ui.lineEdit_twin_machine_x.setText(str(self.machine_x))

        data = "_to_unity_parameters_(specimen_xpos={0};machine_xpos={1};machine_ypos={2};machine_zpos={3};machine_rot={4};eye_xpos={5};eye_ypos={6};eye_zpos={7};)"
        data = data.format(self.specimen_x, self.machine_x, self.machine_y, self.machine_z, self.machine_rot, self.eye_x, self.eye_y, self.eye_z)
        self.server.sendMessage(data)
    
    def onMachineYChanged(self):
        self.machine_y = self.ui.horizontalSlider_twin_machine_y.value() * 0.01
        self.ui.lineEdit_twin_machine_y.setText(str(self.machine_y))

        data = "_to_unity_parameters_(specimen_xpos={0};machine_xpos={1};machine_ypos={2};machine_zpos={3};machine_rot={4};eye_xpos={5};eye_ypos={6};eye_zpos={7};)"
        data = data.format(self.specimen_x, self.machine_x, self.machine_y, self.machine_z, self.machine_rot, self.eye_x, self.eye_y, self.eye_z)
        self.server.sendMessage(data)

    def onMachineZChanged(self):
        self.machine_z = self.ui.horizontalSlider_twin_machine_z.value() * 0.001
        self.ui.lineEdit_twin_machine_z.setText(str(self.machine_z))

        data = "_to_unity_parameters_(specimen_xpos={0};machine_xpos={1};machine_ypos={2};machine_zpos={3};machine_rot={4};eye_xpos={5};eye_ypos={6};eye_zpos={7};)"
        data = data.format(self.specimen_x, self.machine_x, self.machine_y, self.machine_z, self.machine_rot, self.eye_x, self.eye_y, self.eye_z)
        self.server.sendMessage(data)
    
    def onMachineRotChanged(self):
        self.machine_rot = self.ui.horizontalSlider_twin_machine_rot.value()
        self.ui.lineEdit_twin_machine_rot.setText(str(self.machine_rot))

        data = "_to_unity_parameters_(specimen_xpos={0};machine_xpos={1};machine_ypos={2};machine_zpos={3};machine_rot={4};eye_xpos={5};eye_ypos={6};eye_zpos={7};)"
        data = data.format(self.specimen_x, self.machine_x, self.machine_y, self.machine_z, self.machine_rot, self.eye_x, self.eye_y, self.eye_z)
        self.server.sendMessage(data)
    
    def onEyeXChanged(self):
        self.eye_x = self.ui.horizontalSlider_twin_eye_x.value() * 0.001
        self.ui.lineEdit_twin_eye_x.setText(str(self.eye_x))

        data = "_to_unity_parameters_(specimen_xpos={0};machine_xpos={1};machine_ypos={2};machine_zpos={3};machine_rot={4};eye_xpos={5};eye_ypos={6};eye_zpos={7};)"
        data = data.format(self.specimen_x, self.machine_x, self.machine_y, self.machine_z, self.machine_rot, self.eye_x, self.eye_y, self.eye_z)
        self.server.sendMessage(data)
    
    def onEyeYChanged(self):
        self.eye_y = self.ui.horizontalSlider_twin_eye_y.value() * 0.01
        self.ui.lineEdit_twin_eye_y.setText(str(self.eye_y))

        data = "_to_unity_parameters_(specimen_xpos={0};machine_xpos={1};machine_ypos={2};machine_zpos={3};machine_rot={4};eye_xpos={5};eye_ypos={6};eye_zpos={7};)"
        data = data.format(self.specimen_x, self.machine_x, self.machine_y, self.machine_z, self.machine_rot, self.eye_x, self.eye_y, self.eye_z)
        self.server.sendMessage(data)

    def onEyeZChanged(self):
        self.eye_z = self.ui.horizontalSlider_twin_eye_z.value() * 0.01
        self.ui.lineEdit_twin_eye_z.setText(str(self.eye_z))

        data = "_to_unity_parameters_(specimen_xpos={0};machine_xpos={1};machine_ypos={2};machine_zpos={3};machine_rot={4};eye_xpos={5};eye_ypos={6};eye_zpos={7};)"
        data = data.format(self.specimen_x, self.machine_x, self.machine_y, self.machine_z, self.machine_rot, self.eye_x, self.eye_y, self.eye_z)
        self.server.sendMessage(data)

class TwinSerialConnectionDialog(QDialog):
    def __init__(self, Serial):
        super().__init__()
        self.Serial = Serial
        self.setWindowTitle("Serial Port Connections")
        self.resize(400, 200)
        
        layout = QVBoxLayout()

        # Add a QLabel to display the selected Serial Port
        self.serial_port_label = QLabel("Selected Serial Port: ", self)
        layout.addWidget(self.serial_port_label)

        # Add a QComboBox to select a Serial Port
        self.serial_port_combobox = QComboBox(self)
        self.serial_port_combobox.setStyleSheet(twin_qcombox_style)
        available_ports = QSerialPortInfo.availablePorts()
        ports = []
        for port in available_ports:
            ports.append(port.portName())
        self.serial_port_combobox.addItems(ports)
        layout.addWidget(self.serial_port_combobox)

        # Add a QPushButton to connect to the selected Serial Port
        self.connect_button = QPushButton("Connect", self)
        self.connect_button.clicked.connect(self.connect_to_serial_port)
        layout.addWidget(self.connect_button)

        self.setLayout(layout)

    def connect_to_serial_port(self):
        # 连接所选串口
        self.Serial.setPortName(self.serial_port_combobox.currentText())
        self.Serial.setBaudRate(QSerialPort.Baud115200)
        self.Serial.setDataBits(QSerialPort.Data8)
        self.Serial.setParity(QSerialPort.NoParity)
        self.Serial.setStopBits(QSerialPort.OneStop)
        self.Serial.setFlowControl(QSerialPort.NoFlowControl)
        if self.Serial.open(QSerialPort.ReadWrite) == True:
            self.accept()
        else:
            self.reject()

def map_range(value, in_min, in_max, out_min, out_max):
    return np.interp(value, (in_min, in_max), (out_min, out_max))


if __name__ == '__main__':
    # QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)

    app = QApplication(sys.argv)
    # win = DialogWindow()
    # win = TwinSerialConnectionDialog()
    win = InterfaceWindow()
    sys.exit(app.exec_())
